package top.goldenyear.porpoise.dev.code.util;

import static cn.hutool.v7.core.text.CharSequenceUtil.*;

import cn.hutool.v7.core.collection.CollUtil;
import cn.hutool.v7.core.map.MapUtil;
import cn.hutool.v7.core.reflect.FieldUtil;
import cn.hutool.v7.core.text.StrUtil;
import top.goldenyear.porpoise.dev.code.model.CodeColumn;
import top.goldenyear.porpoise.dev.code.model.CodeGen;
import top.goldenyear.porpoise.dev.code.model.CodeTable;
import top.goldenyear.porpoise.framework.common.util.object.BeanUtils;
import top.goldenyear.porpoise.framework.orm.core.model.BaseDo;
import top.goldenyear.porpoise.framework.security.core.util.SecurityUtils;
import top.goldenyear.porpoise.framework.web.core.generator.config.ColumnConfig;
import top.goldenyear.porpoise.framework.web.core.generator.config.TableConfig;
import top.goldenyear.porpoise.framework.web.core.generator.enums.FormElement;
import top.goldenyear.porpoise.framework.web.core.generator.enums.Scene;
import top.goldenyear.porpoise.framework.web.core.generator.enums.TemplateType;
import top.goldenyear.porpoise.framework.web.core.generator.model.ColumnMeta;
import top.goldenyear.porpoise.framework.web.core.generator.model.TableMeta;
import top.goldenyear.porpoise.framework.web.core.model.query.QOperator;

import java.time.LocalDateTime;
import java.util.*;

/**
 * 代码生成器辅助类
 *
 * <p>1. 将数据库的表 TableMeta 定义，构建 CodeTable，增加默认值
 *
 * <p>2. 将数据库的列 ColumnMeta 定义，构建 CodeColumn，增加默认值
 *
 * <p>3. 用CodeTable 构建 TableMeta 和 TableConfig
 *
 * <p>4. 用CodeColumn 构建 ColumnMeta 和 ColumnConfig
 *
 * @author airhead
 */
public class CodeGenUtils {

  /** 多租户编号的字段名 */
  public static final String TENANT_ID_FIELD = "tenantId";

  public static final Set<String> BASE_DO_FIELDS = new HashSet<>();

  /** 字段名与 {@link QOperator} 的默认映射 注意，字段的匹配以后缀的方式 */
  private static final Map<String, QOperator> SEARCH_CONDITION_MAPPINGS =
      MapUtil.<String, QOperator>builder()
          .put("name", QOperator.LIKE)
          .put("time", QOperator.BETWEEN)
          .put("date", QOperator.BETWEEN)
          .build();

  /** 字段名与 {@link FormElement} 的默认映射 注意，字段的匹配以后缀的方式 */
  private static final Map<String, FormElement> FORM_ELEMENT_MAPPINGS =
      MapUtil.<String, FormElement>builder()
          .put("status", FormElement.RADIO)
          .put("sex", FormElement.RADIO)
          .put("type", FormElement.SELECT)
          .put("image", FormElement.IMAGE_UPLOAD)
          .put("file", FormElement.FILE_UPLOAD)
          .put("content", FormElement.EDITOR)
          .put("description", FormElement.EDITOR)
          .put("demo", FormElement.EDITOR)
          .put("time", FormElement.DATETIME)
          .put("date", FormElement.DATETIME)
          .build();

  /** 新增操作，不需要传递的字段 */
  private static final Set<String> CREATE_EXCLUDE_COLUMN =
      new HashSet<>() {
        {
          add("id");
        }
      };

  /** 修改操作，不需要传递的字段 */
  private static final Set<String> UPDATE_EXCLUDE_COLUMN = new HashSet<>();

  /** 列表操作的条件，不需要传递的字段 */
  private static final Set<String> SEARCH_EXCLUDE_COLUMN =
      new HashSet<>() {
        {
          add("id");
        }
      };

  /** 列表操作的结果，不需要返回的字段 */
  private static final Set<String> RESULT_EXCLUDE_COLUMN = new HashSet<>();

  static {
    Arrays.stream(FieldUtil.getFields(BaseDo.class))
        .forEach(field -> BASE_DO_FIELDS.add(field.getName()));
    BASE_DO_FIELDS.add(TENANT_ID_FIELD);
    // 处理 OPERATION 相关的字段
    CREATE_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
    UPDATE_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
    SEARCH_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
    // 创建时间，还是可能需要传递的
    SEARCH_EXCLUDE_COLUMN.remove("createTime");
    RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
    // 创建时间，还是需要返回的
    RESULT_EXCLUDE_COLUMN.remove("createTime");
  }

  /**
   * 少变多，需要初始化，所以用 build 开头
   *
   * @param tableMeta
   * @return
   */
  public static CodeTable buildCodeTable(Integer dbId, CodeGen codeGen, TableMeta tableMeta) {
    CodeTable table = new CodeTable();
    table.setName(tableMeta.getName());
    table.setComment(tableMeta.getComment());
    table.setDbId(dbId);
    table.setPrimaryKey(tableMeta.getFirstPrimaryKey());
    table.setBasePackage(codeGen.getBasePackage());
    table.setTableTitle(tableMeta.getComment());

    initTableDefault(table);

    TableConfig tableConfig = TableConfig.of();
    if (StrUtil.isNotBlank(tableConfig.getDomain())) {
      table.setDomain(tableConfig.getDomain());
    }

    table.setTenantTable(false);
    for (ColumnMeta column : tableMeta.getColumns()) {
      if ("tenant_id".equals(column.getName())) {
        table.setTenantTable(true);
        break;
      }
    }

    return table;
  }

  public static List<CodeColumn> buildColumns(Integer tableId, TableMeta tableMeta) {
    List<CodeColumn> codeColumns = BeanUtils.toBean(tableMeta.getColumnMetas(), CodeColumn.class);
    int index = 1;
    Set<String> primaryKeys = tableMeta.getPrimaryKeys();
    for (CodeColumn codeColumn : codeColumns) {
      codeColumn.setTableId(tableId);
      codeColumn.setOrdinalPosition(index++);

      // 初始化 Column 列的默认字段
      processTable(codeColumn);
      processForm(codeColumn);
      if (primaryKeys.contains(codeColumn.getName())) {
        codeColumn.setPrimaryKey(true);
        if (Boolean.TRUE.equals(codeColumn.getAutoIncrement())) {
          codeColumn.setGeneratedKey(true);
        }
      }
    }

    return codeColumns;
  }

  /**
   * 初始化 Table 表的默认字段
   *
   * @param table 表定义
   */
  private static void initTableDefault(CodeTable table) {
    // 以 system_dept 举例子。moduleName 为 system、businessName 为 dept、className 为 Dept
    // 如果希望以 System 前缀，则可以手动在【代码生成 - 修改生成配置 - 基本信息】，将实体类名称改为 SystemDept 即可
    String tableName = table.getName().toLowerCase();
    // 第一步，_ 前缀的前面，作为 module 名字；第二步，moduleName 必须小写；
    String biz = subBefore(tableName, '_', false);
    table.setBiz(biz);
    String domain = subBetween(biz + "_", "_");
    table.setDomain(domain);

    table.setTemplateType(TemplateType.ONE.getType());
    table.setScene(Scene.ADMIN.getValue());
    table.setAuthor(SecurityUtils.getNickName());
  }

  private static void processTable(CodeColumn codeColumn) {
    // 处理 Search 字段
    codeColumn.setSearch(
        !SEARCH_EXCLUDE_COLUMN.contains(codeColumn.getProperty())
            && !codeColumn.getPrimaryKey()); // 对于主键，列表过滤不需要传递

    // 处理 Search Condition 字段
    SEARCH_CONDITION_MAPPINGS.entrySet().stream()
        .filter(entry -> StrUtil.endWithIgnoreCase(codeColumn.getProperty(), entry.getKey()))
        .findFirst()
        .ifPresent(entry -> codeColumn.setSearchOperator(entry.getValue().getValue()));
    if (codeColumn.getSearchOperator() == null) {
      codeColumn.setSearchOperator(QOperator.EQ.getValue());
    }

    // 处理 Result 字段
    codeColumn.setResult(!RESULT_EXCLUDE_COLUMN.contains(codeColumn.getProperty()));
    codeColumn.setSearchSortRank(codeColumn.getOrdinalPosition());
    codeColumn.setResultLabel(codeColumn.getComment());
  }

  private static void processForm(CodeColumn codeColumn) {
    // 处理 createOperation 字段
    codeColumn.setFormCreate(
        !CREATE_EXCLUDE_COLUMN.contains(codeColumn.getProperty())
            && !codeColumn.getPrimaryKey()); // 对于主键，创建时无需传递
    // 处理 updateOperation 字段
    codeColumn.setFormUpdate(
        !UPDATE_EXCLUDE_COLUMN.contains(codeColumn.getProperty())
            || codeColumn.getPrimaryKey()); // 对于主键，更新时需要传递

    // 基于后缀进行匹配
    FORM_ELEMENT_MAPPINGS.entrySet().stream()
        .filter(entry -> StrUtil.endWithIgnoreCase(codeColumn.getProperty(), entry.getKey()))
        .findFirst()
        .ifPresent(entry -> codeColumn.setFormElement(entry.getValue().getType()));
    // 如果是 Boolean 类型时，设置为 radio 类型.
    if (Boolean.class.getSimpleName().equals(codeColumn.getPropertySimpleType())) {
      codeColumn.setFormElement(FormElement.RADIO.getType());
    }
    // 如果是 LocalDateTime 类型，则设置为 datetime 类型
    if (LocalDateTime.class.getSimpleName().equals(codeColumn.getPropertySimpleType())) {
      codeColumn.setFormElement(FormElement.DATETIME.getType());
    }
    // 兜底，设置默认为 input 类型
    if (codeColumn.getFormElement() == null) {
      codeColumn.setFormElement(FormElement.INPUT.getType());
    }

    codeColumn.setFormLabel(codeColumn.getComment());
    codeColumn.setFormSortRank(codeColumn.getOrdinalPosition());

    boolean isNum =
        "INT".equals(codeColumn.getType())
            || "BIGINT".equals(codeColumn.getType())
            || "TINYINT".equals(codeColumn.getType());
    if ("id".equals(codeColumn.getName()) && isNum) {
      codeColumn.setModelDefValue("0");
    } else {

      codeColumn.setModelDefValue("null");
    }
  }

  public static TableMeta buildTableMeta(CodeTable codeTable) {
    return BeanUtils.toBean(codeTable, TableMeta.class);
  }

  public static TableConfig buildTableConfig(CodeTable codeTable) {
    TableConfig tableConfig = BeanUtils.toBean(codeTable, TableConfig.class);
    tableConfig.setTableName(codeTable.getName());
    return tableConfig;
  }

  public static List<ColumnMeta> buildColumnMeta(List<CodeColumn> codeColumns) {
    if (CollUtil.isEmpty(codeColumns)) {
      return new ArrayList<>();
    }

    return BeanUtils.toBean(codeColumns, ColumnMeta.class);
  }

  public static ColumnConfig buildColumnConfig(CodeTable codeTable, CodeColumn codeColumn) {
    ColumnConfig columnConfig = BeanUtils.toBean(codeColumn, ColumnConfig.class);
    columnConfig.setTableName(codeTable.getName());
    columnConfig.setColumnName(codeColumn.getName());
    return columnConfig;
  }
}
